#include"udpServer.hpp"
#include<memory>
#include<unordered_map>
#include<fstream>
#include<signal.h>

const string dictTxt = "./dict.txt";
unordered_map<string, string> dict;

//走到这里就是外部调用错误，没有传应该传的参数进来, 这里就提示一下
static void Usage(string proc)
{
    cout << "Usage: \n" << proc << "local_port\n";
}

static bool cutString(const string& target, string* s1, string* s2)
{
    // 将target按照目标切割符，切给s1和s2两个字符串
    string sep = ":";
    auto pos = target.find(sep);
    if(pos == string::npos)
        return false; //走到这就是没有找到目标分割符

    *s1 = target.substr(0, pos); //substr的参数是前闭后开的区间 [), 因此实际上切割的下标是[0, pos)
    // *s2 = target.substr(pos + sep.size(), target.size()); // pos + sep.size()s是因为sep分隔符的长度不一定是固定的
    *s2 = target.substr(pos + sep.size()); // 等价于上面，反正是切到结尾，不填第二个参数也行
    return true;
}

static void InitDict()
{
    ifstream in(dictTxt, ios::binary);
    if(!in.is_open())
    {
        cerr << "ifstream open " << dictTxt << " error " << endl;
        exit(OPEN_ERR);
    }
    // 走到这, 打开文件成功
    string line;
    string key, value;
    while(getline(in, line))
    {
        // cout << line << endl;
        // 这里对line进行子串截取
        if(cutString(line, &key, &value))
        {
            dict.insert(make_pair(key, value));
        }
    }
    in.close();

    cout << "load dict success\n";
} 

static void debugDictPrint()
{
    for(auto& d : dict)
    {
        cout << d.first << "的中文是" << d.second << endl;
    }
}

//对message进行处理，不关心message如何获取的————server通信和业务逻辑解耦
void handlerMessage(int sockfd, string clientip, uint16_t clientport, string message)
{
    //这里我做的业务逻辑是翻译，将传过来的英文单词翻译成中文然后再返回给客户端
    // 婴儿般的业务逻辑，hhh
    string response_message;
    auto iter = dict.find(message); //message是客户端传来的信息，拿到字典中查找对应中文翻译
    if(iter == dict.end())
        response_message = "unknown word";
    else
        response_message = iter->second;

    // 处理完之后，将信息返回给客户端【需要客户端的ip和port，以及协议等信息】
    struct sockaddr_in client; //填充结构体，如果懒得填充，可以给该函数设置一个参数，在外部传进来，可以直接用
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = inet_addr(clientip.c_str()); //点分十进制ip -> int类4字节ip
    client.sin_port = htons(clientport); // 转网络字节序

    sendto(sockfd, response_message.c_str(), response_message.size(), 0, (struct sockaddr*)&client, sizeof(sockaddr_in));

    return;
}

//能够处理客户端发来的命令，并返回执行命令后的结果回去
void execcommand(int sockfd, string clientip, uint16_t clientport, string cmd)
{
    // cmd解析，将接收到的命令进行解析，ls -a -l
    // fork 然后 exec*
    // 【如果愿意的话，可以将之前实现的myshell，直接拿过来，将里面的向stdout输出的逻辑，全部更改为向网络输出】
    // 这里为了方便，学习一个新接口 popen！[FILE *popen(const char *command, const char *type)]
    // popen一个函数内部就实现了 fork + exec* + pipe三个功能
    //【创建子进程，将输入的command命令字符串， 进行解析并用exec*进程替换函数将命令执行，然后将执行后的结果通过pipe返回回来(popen的返回值就是一个FILE*)】

    string response;
    FILE* fp = popen(cmd.c_str(), "r");
    if(fp == nullptr) //这里失败有可能是fork失败，有可能是exec*失败
        response = cmd + " exec failed";
    char line[1024];
    while(fgets(line, sizeof(line), fp))
    {
        response += line;
    }
    pclose(fp);
    
    // 处理完之后，将信息返回给客户端【需要客户端的ip和port，以及协议等信息】
    struct sockaddr_in client;
    bzero(&client, sizeof(client));
    client.sin_family = AF_INET;
    client.sin_addr.s_addr = inet_addr(clientip.c_str());
    client.sin_port = htons(clientport);

    sendto(sockfd, response.c_str(), response.size(), 0, (struct sockaddr*)&client, sizeof(client));
}



void reload(int signo)
{
    (void)signo;
    InitDict(); //重新加载一次字典
}

// 在调用这个进程的时候要传port进来【也可以显式传ip，然后给服务器bind，但是不推荐这样做】
// ./udpServer port
int main(int argc, char* argv[])
{
    if(argc != 2) //必须得是2个元素，进程名，port。【这是命令行参数的知识，忘了就复习】
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    // 将argv中char*类型的port记载下来
    uint16_t port = atoi(argv[1]); //必须转成int类型，因为argv里面全是char*类型的元素
    
    signal(3, reload); //对3号信号做一个捕捉, 这样就支持了一个简单的热加载【只要捕捉到信号3，就可以重新调用一次InitDict】

    // InitDict(); //将字典数据从文件中读取出来
    // debugDictPrint();
    //创建服务器对象
    // std::unique_ptr<udpServer> usvr(new udpServer(handlerMessage, port)); //智能指针管理
    std::unique_ptr<udpServer> usvr(new udpServer(execcommand, port)); 
    usvr->initServer(); //初始化服务器
    usvr->start(); //启动服务器
    
    return 0;
}